/*
 * Fast QR Code generator library
 *
 * Copyright (c) Project Nayuki. (MIT License)
 * https://www.nayuki.io/page/fast-qr-code-generator-library
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * - The above copyright notice and this permission notice shall be included in
 *   all copies or substantial portions of the Software.
 * - The Software is provided "as is", without warranty of any kind, express or
 *   implied, including but not limited to the warranties of merchantability,
 *   fitness for a particular purpose and noninfringement. In no event shall the
 *   authors or copyright holders be liable for any claim, damages or other
 *   liability, whether in an action of contract, tort or otherwise, arising from,
 *   out of or in connection with the Software or the use or other dealings in the
 *   Software.
 */

package com.ajaxjs.qr_code.fast;

/**
 * Stores the parts of a QR Code that depend only on the version number, and does not depend on the data or error correction level or mask.
 */
public final class QrTemplate {
    // Use this memoizer to get instances of this class.
    public static final Memoizer<Integer, QrTemplate> MEMOIZER = new Memoizer<>(QrTemplate::new);

    private final int version;  // In the range [1, 40].

    /**
     * Derived from version.
     */
    private final int size;

    public final int[] template;  // Length and values depend on version.
    public final int[][] masks;  // masks.length == 8, and masks[i].length == template.length.
    public final int[] dataOutputBitIndexes;  // Length and values depend on version.

    // Indicates function modules that are not subjected to masking. Discarded when constructor finishes.
    // Otherwise, when the constructor is running, isFunction.length == template.length.
    private int[] isFunction;

    /**
     * Creates a QR Code template for the given version number.
     *
     * @param ver
     */
    private QrTemplate(int ver) {
        if (ver < QrCodeFast.MIN_VERSION || ver > QrCodeFast.MAX_VERSION) throw new IllegalArgumentException("Version out of range");

        version = ver;
        size = version * 4 + 17;
        template = new int[(size * size + 31) / 32];
        isFunction = new int[template.length];

        drawFunctionPatterns();  // Reads and writes fields
        masks = generateMasks();  // Reads fields, returns array
        dataOutputBitIndexes = generateZigzagScan();  // Reads fields, returns array
        isFunction = null;
    }

    // Reads this object's version field, and draws and marks all function modules.
    private void drawFunctionPatterns() {
        // Draw horizontal and vertical timing patterns
        for (int i = 0; i < size; i++) {
            darkenFunctionModule(6, i, ~i & 1);
            darkenFunctionModule(i, 6, ~i & 1);
        }

        // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules)
        drawFinderPattern(3, 3);
        drawFinderPattern(size - 4, 3);
        drawFinderPattern(3, size - 4);

        // Draw numerous alignment patterns
        int[] alignPatPos = getAlignmentPatternPositions();
        int numAlign = alignPatPos.length;

        for (int i = 0; i < numAlign; i++) {
            for (int j = 0; j < numAlign; j++) {
                // Don't draw on the three finder corners
                if (!(i == 0 && j == 0 || i == 0 && j == numAlign - 1 || i == numAlign - 1 && j == 0))
                    drawAlignmentPattern(alignPatPos[i], alignPatPos[j]);
            }
        }

        // Draw configuration data
        drawDummyFormatBits();
        drawVersion();
    }

    /**
     * Draws two blank copies of the format bits.
     */
    private void drawDummyFormatBits() {
        // Draw first copy
        for (int i = 0; i <= 5; i++)
            darkenFunctionModule(8, i, 0);
        darkenFunctionModule(8, 7, 0);
        darkenFunctionModule(8, 8, 0);
        darkenFunctionModule(7, 8, 0);
        for (int i = 9; i < 15; i++)
            darkenFunctionModule(14 - i, 8, 0);

        // Draw second copy
        for (int i = 0; i < 8; i++)
            darkenFunctionModule(size - 1 - i, 8, 0);
        for (int i = 8; i < 15; i++)
            darkenFunctionModule(8, size - 15 + i, 0);

        darkenFunctionModule(8, size - 8, 1);  // Always dark
    }

    /**
     * Draws two copies of the version bits (with its own error correction code),  based on this object's version field, iff 7 <= version <= 40.
     */
    private void drawVersion() {
        if (version < 7) return;

        // Calculate error correction code and pack bits
        int rem = version;  // version is uint6, in the range [7, 40]
        for (int i = 0; i < 12; i++)
            rem = (rem << 1) ^ ((rem >>> 11) * 0x1F25);
        int bits = version << 12 | rem;  // uint18
        assert bits >>> 18 == 0;

        // Draw two copies
        for (int i = 0; i < 18; i++) {
            int bit = QrCodeFast.getBit(bits, i);
            int a = size - 11 + i % 3;
            int b = i / 3;
            darkenFunctionModule(a, b, bit);
            darkenFunctionModule(b, a, bit);
        }
    }

    /**
     * Draws a 9*9 finder pattern including the border separator,  with the center module at (x, y). Modules can be out of bounds.
     *
     * @param x
     * @param y
     */
    private void drawFinderPattern(int x, int y) {
        for (int dy = -4; dy <= 4; dy++) {
            for (int dx = -4; dx <= 4; dx++) {
                int dist = Math.max(Math.abs(dx), Math.abs(dy));  // Chebyshev/infinity norm
                int xx = x + dx, yy = y + dy;
                if (0 <= xx && xx < size && 0 <= yy && yy < size) darkenFunctionModule(xx, yy, (dist != 2 && dist != 4) ? 1 : 0);
            }
        }
    }

    /**
     * Draws a 5*5 alignment pattern, with the center module at (x, y). All modules must be in bounds.
     *
     * @param x
     * @param y
     */
    private void drawAlignmentPattern(int x, int y) {
        for (int dy = -2; dy <= 2; dy++) {
            for (int dx = -2; dx <= 2; dx++)
                darkenFunctionModule(x + dx, y + dy, Math.abs(Math.max(Math.abs(dx), Math.abs(dy)) - 1));
        }
    }

    /**
     * Computes and returns a new array of masks, based on this object's various fields.
     *
     * @return
     */
    private int[][] generateMasks() {
        int[][] result = new int[8][template.length];

        for (int mask = 0; mask < result.length; mask++) {
            int[] maskModules = result[mask];
            for (int y = 0, i = 0; y < size; y++) {
                for (int x = 0; x < size; x++, i++) {
                    boolean invert;
                    switch (mask) {
                        case 0:
                            invert = (x + y) % 2 == 0;
                            break;
                        case 1:
                            invert = y % 2 == 0;
                            break;
                        case 2:
                            invert = x % 3 == 0;
                            break;
                        case 3:
                            invert = (x + y) % 3 == 0;
                            break;
                        case 4:
                            invert = (x / 3 + y / 2) % 2 == 0;
                            break;
                        case 5:
                            invert = x * y % 2 + x * y % 3 == 0;
                            break;
                        case 6:
                            invert = (x * y % 2 + x * y % 3) % 2 == 0;
                            break;
                        case 7:
                            invert = ((x + y) % 2 + x * y % 3) % 2 == 0;
                            break;
                        default:
                            throw new AssertionError();
                    }

                    int bit = (invert ? 1 : 0) & ~getModule(isFunction, x, y);
                    maskModules[i >>> 5] |= bit << i;
                }
            }
        }

        return result;
    }

    /**
     * Computes and returns an array of bit indexes, based on this object's various fields.
     *
     * @return
     */
    private int[] generateZigzagScan() {
        int[] result = new int[getNumRawDataModules(version) / 8 * 8];
        int i = 0;  // Bit index into the data

        for (int right = size - 1; right >= 1; right -= 2) {  // Index of right column in each column pair
            if (right == 6) right = 5;

            for (int vert = 0; vert < size; vert++) {  // Vertical counter
                for (int j = 0; j < 2; j++) {
                    int x = right - j;  // Actual x coordinate
                    boolean upward = ((right + 1) & 2) == 0;
                    int y = upward ? size - 1 - vert : vert;  // Actual y coordinate

                    if (getModule(isFunction, x, y) == 0 && i < result.length) {
                        result[i] = y * size + x;
                        i++;
                    }
                }
            }
        }

        assert i == result.length;

        return result;
    }

    /**
     * Returns the value of the bit at the given coordinates in the given grid.
     *
     * @param grid
     * @param x
     * @param y
     * @return
     */
    private int getModule(int[] grid, int x, int y) {
        assert 0 <= x && x < size;
        assert 0 <= y && y < size;
        int i = y * size + x;
        return QrCodeFast.getBit(grid[i >>> 5], i);
    }


    /**
     * Marks the module at the given coordinates as a function module.Also either sets that module dark or keeps its color unchanged.
     *
     * @param x
     * @param y
     * @param enable
     */
    private void darkenFunctionModule(int x, int y, int enable) {
        assert 0 <= x && x < size;
        assert 0 <= y && y < size;
        assert enable == 0 || enable == 1;
        int i = y * size + x;
        template[i >>> 5] |= enable << i;
        isFunction[i >>> 5] |= 1 << i;
    }

    /**
     * Returns an ascending list of positions of alignment patterns for this version number.
     * Each position is in the range [0,177), and are used on both the x and y axes.
     * This could be implemented as lookup table of 40 variable-length lists of unsigned bytes.
     *
     * @return
     */
    private int[] getAlignmentPatternPositions() {
        if (version == 1) return new int[]{};
        else {
            int numAlign = version / 7 + 2;
            int step = (version == 32) ? 26 : (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2;
            int[] result = new int[numAlign];
            result[0] = 6;
            for (int i = result.length - 1, pos = size - 7; i >= 1; i--, pos -= step)
                result[i] = pos;

            return result;
        }
    }

    /**
     * Returns the number of data bits that can be stored in a QR Code of the given version number, after
     * all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8.
     * The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table.
     *
     * @param ver
     * @return
     */
    public static int getNumRawDataModules(int ver) {
        if (ver < QrCodeFast.MIN_VERSION || ver > QrCodeFast.MAX_VERSION) throw new IllegalArgumentException("Version number out of range");
        int result = (16 * ver + 128) * ver + 64;

        if (ver >= 2) {
            int numAlign = ver / 7 + 2;
            result -= (25 * numAlign - 10) * numAlign - 55;
            if (ver >= 7) result -= 36;
        }

        return result;
    }
}